package org.ccay.i18n.service.impl;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.inject.Inject;
import javax.inject.Named;

import org.ccay.core.dispatcher.EventUtil;
import org.ccay.core.exception.ApplicationException;
import org.ccay.core.log.CcayLoggerFactory;
import org.ccay.core.log.ILogger;
import org.ccay.core.request.IApplicationPrincipal;
import org.ccay.core.request.IRequestContext;
import org.ccay.core.request.impl.RequestContext;
import org.ccay.core.util.StringUtil;
import org.ccay.i18n.I18nVO;
import org.ccay.i18n.dao.II18nLoadDao;
import org.ccay.i18n.events.II18nChangedEventArgs;
import org.ccay.i18n.events.II18nChangedHandler;
import org.ccay.i18n.service.ReloadableResourceBundle;
import org.ccay.registry.util.RegistryQueryUtil;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.DefaultPropertiesPersister;
import org.springframework.util.PropertiesPersister;

/**
 * 查找classpath上的config/*_i18n*.properties文件配置获取所有国际化信息及数据库中获取国际化信息放入到Map缓存中；
 * 读取时，先读取数据库中加载的国际化信息（支持非停机更改国际化场景）如果未找到再从properties的缓存中读取；
 * perpertis文件
 */
@Named
public class I18nResourceBundle extends ResourceBundle implements II18nChangedHandler,ReloadableResourceBundle {
	
	@Inject
	private II18nLoadDao i18nLoadDao;
	
	@Inject
	private IApplicationPrincipal application;
	
	
	private static ILogger logger = CcayLoggerFactory.getLogger(I18nResourceBundle.class);
	
	/**
	 * Properties加载使用
	 */
	private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister();
	
	/**
	 * 文件编码
	 */
	private String encoding = "UTF-8";
	
	/**
	 * 资源加载状态
	 */
	private static final ConcurrentMap<String,BundleStatus> bundleStatus = new ConcurrentHashMap<String,BundleStatus>();
	
	/**
	 * properties文件中的国际化信息 语种和资源MAP
	 */
	private static final ConcurrentMap<String,Map<String,Object>> 
		propertiesLangMap = new ConcurrentHashMap<String, Map<String,Object>>();
	
	/**
	 * 数据库中的国际化信息 语种和资源MAP
	 */
	private static final ConcurrentMap<String,Map<String,Object>> 
		dbLangMap = new ConcurrentHashMap<String, Map<String,Object>>();
	
	private void initFromI18nFile(String userLanguage){
		// 查找classpath上的config/*_i18n*.properties文件配置
		ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
		Resource[] resources;
		try {
			resources = resourcePatternResolver
					.getResources("classpath*:config/i18n.*_"+userLanguage+".properties");
			for (Resource resource : resources) {
				if(resource.exists()){
					if(propertiesLangMap.get(userLanguage) == null){
						propertiesLangMap.put(userLanguage, new HashMap<String,Object>());
					}
					propertiesLangMap.get(userLanguage).putAll(loadFromResource(resource));
				}
			}
		} catch (IOException ioe) {
			logger.error(ioe);
		}
	}
	
	
	/**
	 * 从文件加载国际化
	 * @param resource
	 * @return
	 */
	private Map<String,Object> loadFromResource(Resource resource) throws IOException{
		InputStream is = resource.getInputStream();
		Properties props = new Properties();
		Map<String,Object> map = new HashMap<String,Object>();
		try {
			if (logger.isDebugEnabled()) {
				logger.debug("Loading properties [" + resource.getFilename() + "] with encoding '" + encoding + "'");
			}
			this.propertiesPersister.load(props, new InputStreamReader(is, encoding));
			for(Map.Entry<Object, Object> entry : props.entrySet()){
				map.put(entry.getKey().toString(), entry.getValue().toString());
			}
			return map;
		}
		finally {
			is.close();
		}
	}
	
	/**
	 * 从数据库加载国际化资源
	 * @param userLanguage
	 */
	private void initFromDatabase(String userLanguage) {
		List<I18nVO> i18nList = i18nLoadDao.findI18nList(userLanguage);
		Map<String,Object> map = new HashMap<String,Object>();
		for(I18nVO vo : i18nList){
			map.put(vo.getI18nKey(), vo.getI18nContent());
		}
		if(dbLangMap.get(userLanguage) == null){
			dbLangMap.put(userLanguage, new HashMap<String,Object>());
		}
		dbLangMap.get(userLanguage).putAll(map);
	}
	

    public Object handleGetObject(String key) {
        if (key == null) {
            throw new NullPointerException();
        }
        Map<String, Object> map = getResourceMapOfCurrentLanguage(); 
		Object v =  map.get(key);
		if( v == null){
			return StringUtil.EMPTY;
		}
		
		return v;
    }

	/** 
	 * 获取当前语种的资源MAP
	 * 
	 * @since 2012-7-26
	 * @return
	 */
	private Map<String, Object> getResourceMapOfCurrentLanguage() {
		String userLanguage = RegistryQueryUtil.getValue("Ccay.GlobalParamters.DefaultLanguage");
		IRequestContext context =  RequestContext.getCurrent(true);
		if(context!=null){
			userLanguage = RequestContext.getCurrent().getUserLanguage();
		}else{
			logger.debug("Can not find user's current language use default, cause by CurrentRequestContext is null!");
		}
		return getResourceMap(userLanguage);
	}
	
	
	/** 
	 * 获取指定语种的资源MAP
	 * 
	 * @since 2012-7-26
	 * @return
	 */
	private Map<String, Object> getResourceMap(String userLanguage) {
		if(bundleStatus.get(userLanguage) == null){
			init(userLanguage);
		}
		while(bundleStatus.get(userLanguage) != BundleStatus.LOADED){
			try {
				Thread.sleep(500L);
			} catch (InterruptedException e) {
				//ignore
			}
		}
		return merge(propertiesLangMap.get(userLanguage),dbLangMap.get(userLanguage));
	}
	

	public List<I18nVO> getMergedI18nVO(String i18nLanguage){
		Map<String, Object> merged = getResourceMap(i18nLanguage);
		List<I18nVO> result = new ArrayList<I18nVO>();
		for(Map.Entry<String, Object> entry : merged.entrySet()){
			I18nVO vo = new I18nVO();
			vo.setI18nKey(entry.getKey());
			vo.setI18nContent(entry.getValue() == null ? entry.getKey() : entry.getValue().toString());
			result.add(vo);
		}
		return result;
	}
	

	public I18nVO getI18nVO(String i18nKey, String i18nLanguage) {
		Object value = getResourceMap(i18nLanguage).get(i18nKey);
		I18nVO vo = new I18nVO();
		vo.setI18nKey(i18nKey);
		vo.setI18nContent(value == null ? i18nKey : value.toString());
		return vo;
	}
	
	public I18nVO getI18nVO(String i18nKey, String i18nLanguage,String defaultContext) {
		Object value = getResourceMap(i18nLanguage).get(i18nKey);
		I18nVO vo = new I18nVO();
		vo.setI18nKey(i18nKey);
		vo.setI18nContent(value == null ? defaultContext : value.toString());
		return vo;
	}
	
    /**
     * 合并国际化信息
     * @param map
     * @param map2
     * @return
     */
    private Map<String, Object> merge(Map<String, Object> map,
			Map<String, Object> map2) {
		Map<String,Object> merge = new HashMap<String,Object>(map);
		if(map2 == null){
    		return merge;
    	}
		merge.putAll(map2);
		return merge;
	}

	/**
     * Returns an <code>Enumeration</code> of the keys contained in
     * this <code>ResourceBundle</code> and its parent bundles.
     *
     * @return an <code>Enumeration</code> of the keys contained in
     *         this <code>ResourceBundle</code> and its parent bundles.
     * @see #keySet()
     */
    public Enumeration<String> getKeys() {
        Map<String, Object> map = getResourceMapOfCurrentLanguage(); 
        
        return new ResourceBundleEnumeration(map.keySet(),null);
    }

    /**
     * Returns a <code>Set</code> of the keys contained
     * <em>only</em> in this <code>ResourceBundle</code>.
     *
     * @return a <code>Set</code> of the keys contained only in this
     *         <code>ResourceBundle</code>
     * @since 1.6
     * @see #keySet()
     */
    protected Set<String> handleKeySet() {
    	Map<String, Object> map = getResourceMapOfCurrentLanguage(); 
		return map.keySet();
	}
 

	/**
	 * @return
	 * @throws Exception
	 */
	public ResourceBundle initialize() {
		return this;
	}

	/** 
	 * 同步初始化
	 * 
	 * @since 2012-7-26
	 * @param userLanguage
	 */
	private void init(String userLanguage) {
		if(bundleStatus.get(userLanguage) != null && bundleStatus.get(userLanguage) == BundleStatus.LOADING){
			return ;
		}
		propertiesLangMap.remove(userLanguage);
		dbLangMap.remove(userLanguage);
		bundleStatus.put(userLanguage, BundleStatus.LOADING);
		initFromI18nFile(userLanguage);
		initFromDatabase(userLanguage);
		bundleStatus.put(userLanguage, BundleStatus.LOADED);
	}
	
	
	/**
	 * Implements an Enumeration that combines elements from a Set and
	 * an Enumeration. Used by ListResourceBundle and PropertyResourceBundle.
	 */
	private class ResourceBundleEnumeration implements Enumeration<String> {
	    Set<String> set;
	    Iterator<String> iterator;
	    Enumeration<String> enumeration; // may remain null

	    /**
	     * Constructs a resource bundle enumeration.
	     * @param set an set providing some elements of the enumeration
	     * @param enumeration an enumeration providing more elements of the enumeration.
	     *        enumeration may be null.
	     */
	    ResourceBundleEnumeration(Set<String> set, Enumeration<String> enumeration) {
	        this.set = set;
	        this.iterator = set.iterator();
	        this.enumeration = enumeration;
	    }

	    String next = null;
	            
	    /* (non-Javadoc)
	     * @see java.util.Enumeration#hasMoreElements()
	     */
	    public boolean hasMoreElements() {
	    	if (next != null) {
	    		return true;
	    	}
            if (iterator.hasNext()) {
                next = iterator.next();
    	        return next != null;
            }
            if (enumeration == null) {
	    		return false;
            }

            next = hasMoreInSet(next, enumeration, set);
	        return next != null;
	    }

		/**
		 * @param next
		 * @param enumeration
		 * @param set
		 * @return
		 */
		private String hasMoreInSet(String next, Enumeration<String> enumeration, Set<String> set) {
			while (next == null && enumeration.hasMoreElements()) {
                next = enumeration.nextElement();
                if (set.contains(next)) {
                    next = null;
                }
            }
	        return next;
		}

	    /* (non-Javadoc)
	     * @see java.util.Enumeration#nextElement()
	     */
	    public String nextElement() {
	        if (hasMoreElements()) {
	            String result = next;
	            next = null;
	            return result;
	        } else {
	            throw new NoSuchElementException();
	        }
	    }
	}


	@Override
	public void reload() throws ApplicationException {
		//并不真正处理重新加载，委派给时间处理机制进行处理
		II18nChangedEventArgs args = new II18nChangedEventArgs();
		EventUtil.dispatch(II18nChangedHandler.class, this,args,true);
	}


	@Override
	public void execute(Object sender, II18nChangedEventArgs args) throws ApplicationException {
		propertiesLangMap.clear();
		dbLangMap.clear();
		bundleStatus.clear();
	}
}
